package loon.core.graphics.opengl;

import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.zip.CRC32;
import java.util.zip.DataFormatException;
import java.util.zip.Inflater;

import org.lwjgl.BufferUtils;

public class PNGData {

	static class PNGDecoder {

		public static Format ALPHA = new Format(1, true);
		public static Format LUMINANCE = new Format(1, false);
		public static Format LUMINANCE_ALPHA = new Format(2, true);
		public static Format RGB = new Format(3, false);
		public static Format RGBA = new Format(4, true);
		public static Format BGRA = new Format(4, true);
		public static Format ABGR = new Format(4, true);

		public static class Format {

			final int numComponents;
			final boolean hasAlpha;

			private Format(int numComponents, boolean hasAlpha) {
				this.numComponents = numComponents;
				this.hasAlpha = hasAlpha;
			}

			public int getNumComponents() {
				return numComponents;
			}

			public boolean isHasAlpha() {
				return hasAlpha;
			}
		}

		private static final byte[] SIGNATURE = { (byte) 137, 80, 78, 71, 13,
				10, 26, 10 };

		private static final int IHDR = 0x49484452;
		private static final int PLTE = 0x504C5445;
		private static final int tRNS = 0x74524E53;
		private static final int IDAT = 0x49444154;

		private static final byte COLOR_GREYSCALE = 0;
		private static final byte COLOR_TRUECOLOR = 2;
		private static final byte COLOR_INDEXED = 3;
		private static final byte COLOR_GREYALPHA = 4;
		private static final byte COLOR_TRUEALPHA = 6;

		private final InputStream input;
		private final CRC32 crc;
		private final byte[] buffer;

		private int chunkLength;
		private int chunkType;
		private int chunkRemaining;

		private int width;
		private int height;
		private int bitdepth;
		private int colorType;
		private int bytesPerPixel;
		private byte[] palette;
		private byte[] paletteA;
		private byte[] transPixel;

		public PNGDecoder(InputStream input) throws IOException {
			this.input = input;
			this.crc = new CRC32();
			this.buffer = new byte[4096];

			readFully(buffer, 0, SIGNATURE.length);
			if (!checkSignature(buffer)) {
				throw new IOException("Not a valid PNG file");
			}

			openChunk(IHDR);
			readIHDR();
			closeChunk();

			searchIDAT: for (;;) {
				openChunk();
				switch (chunkType) {
				case IDAT:
					break searchIDAT;
				case PLTE:
					readPLTE();
					break;
				case tRNS:
					readtRNS();
					break;
				}
				closeChunk();
			}

			if (colorType == COLOR_INDEXED && palette == null) {
				throw new IOException("Missing PLTE chunk");
			}
		}

		public int getHeight() {
			return height;
		}

		public int getWidth() {
			return width;
		}

		public boolean hasAlpha() {
			return colorType == COLOR_TRUEALPHA || paletteA != null
					|| transPixel != null;
		}

		public boolean isRGB() {
			return colorType == COLOR_TRUEALPHA || colorType == COLOR_TRUECOLOR
					|| colorType == COLOR_INDEXED;
		}

		public Format decideTextureFormat(Format fmt) {
			switch (colorType) {
			case COLOR_TRUECOLOR:
				if ((fmt == ABGR) || (fmt == RGBA) || (fmt == BGRA)
						|| (fmt == RGB)) {
					return fmt;
				}

				return RGB;
			case COLOR_TRUEALPHA:
				if ((fmt == ABGR) || (fmt == RGBA) || (fmt == BGRA)
						|| (fmt == RGB)) {
					return fmt;
				}

				return RGBA;
			case COLOR_GREYSCALE:
				if ((fmt == LUMINANCE) || (fmt == ALPHA)) {
					return fmt;
				}

				return LUMINANCE;
			case COLOR_GREYALPHA:
				return LUMINANCE_ALPHA;
			case COLOR_INDEXED:
				if ((fmt == ABGR) || (fmt == RGBA) || (fmt == BGRA)) {
					return fmt;
				}

				return RGBA;
			default:
				throw new UnsupportedOperationException("Not yet implemented");
			}
		}

		public void decode(ByteBuffer buffer, int stride, Format fmt)
				throws IOException {
			final int offset = buffer.position();
			final int lineSize = ((width * bitdepth + 7) / 8) * bytesPerPixel;
			byte[] curLine = new byte[lineSize + 1];
			byte[] prevLine = new byte[lineSize + 1];
			byte[] palLine = (bitdepth < 8) ? new byte[width + 1] : null;

			final Inflater inflater = new Inflater();
			try {
				for (int y = 0; y < height; y++) {
					readChunkUnzip(inflater, curLine, 0, curLine.length);
					unfilter(curLine, prevLine);

					buffer.position(offset + y * stride);

					switch (colorType) {
					case COLOR_TRUECOLOR:
						if (fmt == ABGR) {
							copyRGBtoABGR(buffer, curLine);
						} else if (fmt == RGBA) {
							copyRGBtoRGBA(buffer, curLine);
						} else if (fmt == BGRA) {
							copyRGBtoBGRA(buffer, curLine);
						} else if (fmt == RGB) {
							copy(buffer, curLine);
						} else {
							throw new UnsupportedOperationException(
									"Unsupported format for this image");
						}
						break;
					case COLOR_TRUEALPHA:
						if (fmt == ABGR) {
							copyRGBAtoABGR(buffer, curLine);
						} else if (fmt == RGBA) {
							copy(buffer, curLine);
						} else if (fmt == BGRA) {
							copyRGBAtoBGRA(buffer, curLine);
							break;
						} else if (fmt == RGB) {
							copyRGBAtoRGB(buffer, curLine);
							break;
						} else {
							throw new UnsupportedOperationException(
									"Unsupported format for this image");
						}
						break;
					case COLOR_GREYSCALE:
						if ((fmt == LUMINANCE) || (fmt == ALPHA)) {
							copy(buffer, curLine);
						} else {
							throw new UnsupportedOperationException(
									"Unsupported format for this image");
						}
						break;
					case COLOR_GREYALPHA:
						if (fmt == LUMINANCE_ALPHA) {
							copy(buffer, curLine);
						} else {
							throw new UnsupportedOperationException(
									"Unsupported format for this image");
						}
						break;
					case COLOR_INDEXED:
						switch (bitdepth) {
						case 8:
							palLine = curLine;
							break;
						case 4:
							expand4(curLine, palLine);
							break;
						case 2:
							expand2(curLine, palLine);
							break;
						case 1:
							expand1(curLine, palLine);
							break;
						default:
							throw new UnsupportedOperationException(
									"Unsupported bitdepth for this image");
						}
						if (fmt == ABGR) {
							copyPALtoABGR(buffer, palLine);
						} else if (fmt == RGBA) {
							copyPALtoRGBA(buffer, palLine);
						} else if (fmt == BGRA) {
							copyPALtoBGRA(buffer, palLine);
						} else {
							throw new UnsupportedOperationException(
									"Unsupported format for this image");
						}
						break;
					default:
						throw new UnsupportedOperationException(
								"Not yet implemented");
					}

					byte[] tmp = curLine;
					curLine = prevLine;
					prevLine = tmp;
				}
			} finally {
				inflater.end();
			}
		}

		private void copy(ByteBuffer buffer, byte[] curLine) {
			buffer.put(curLine, 1, curLine.length - 1);
		}

		private void copyRGBtoABGR(ByteBuffer buffer, byte[] curLine) {
			if (transPixel != null) {
				byte tr = transPixel[1];
				byte tg = transPixel[3];
				byte tb = transPixel[5];
				for (int i = 1, n = curLine.length; i < n; i += 3) {
					byte r = curLine[i];
					byte g = curLine[i + 1];
					byte b = curLine[i + 2];
					byte a = (byte) 0xFF;
					if (r == tr && g == tg && b == tb) {
						a = 0;
					}
					buffer.put(a).put(b).put(g).put(r);
				}
			} else {
				for (int i = 1, n = curLine.length; i < n; i += 3) {
					buffer.put((byte) 0xFF).put(curLine[i + 2])
							.put(curLine[i + 1]).put(curLine[i]);
				}
			}
		}

		private void copyRGBtoRGBA(ByteBuffer buffer, byte[] curLine) {
			if (transPixel != null) {
				byte tr = transPixel[1];
				byte tg = transPixel[3];
				byte tb = transPixel[5];
				for (int i = 1, n = curLine.length; i < n; i += 3) {
					byte r = curLine[i];
					byte g = curLine[i + 1];
					byte b = curLine[i + 2];
					byte a = (byte) 0xFF;
					if (r == tr && g == tg && b == tb) {
						a = 0;
					}
					buffer.put(r).put(g).put(b).put(a);
				}
			} else {
				for (int i = 1, n = curLine.length; i < n; i += 3) {
					buffer.put(curLine[i]).put(curLine[i + 1])
							.put(curLine[i + 2]).put((byte) 0xFF);
				}
			}
		}

		private void copyRGBtoBGRA(ByteBuffer buffer, byte[] curLine) {
			if (transPixel != null) {
				byte tr = transPixel[1];
				byte tg = transPixel[3];
				byte tb = transPixel[5];
				for (int i = 1, n = curLine.length; i < n; i += 3) {
					byte r = curLine[i];
					byte g = curLine[i + 1];
					byte b = curLine[i + 2];
					byte a = (byte) 0xFF;
					if (r == tr && g == tg && b == tb) {
						a = 0;
					}
					buffer.put(b).put(g).put(r).put(a);
				}
			} else {
				for (int i = 1, n = curLine.length; i < n; i += 3) {
					buffer.put(curLine[i + 2]).put(curLine[i + 1])
							.put(curLine[i]).put((byte) 0xFF);
				}
			}
		}

		private void copyRGBAtoABGR(ByteBuffer buffer, byte[] curLine) {
			for (int i = 1, n = curLine.length; i < n; i += 4) {
				buffer.put(curLine[i + 3]).put(curLine[i + 2])
						.put(curLine[i + 1]).put(curLine[i]);
			}
		}

		private void copyRGBAtoBGRA(ByteBuffer buffer, byte[] curLine) {
			for (int i = 1, n = curLine.length; i < n; i += 4) {
				buffer.put(curLine[i + 2]).put(curLine[i + 1])
						.put(curLine[i + 0]).put(curLine[i + 3]);
			}
		}

		private void copyRGBAtoRGB(ByteBuffer buffer, byte[] curLine) {
			for (int i = 1, n = curLine.length; i < n; i += 4) {
				buffer.put(curLine[i]).put(curLine[i + 1]).put(curLine[i + 2]);
			}
		}

		private void copyPALtoABGR(ByteBuffer buffer, byte[] curLine) {
			if (paletteA != null) {
				for (int i = 1, n = curLine.length; i < n; i += 1) {
					int idx = curLine[i] & 255;
					byte r = palette[idx * 3 + 0];
					byte g = palette[idx * 3 + 1];
					byte b = palette[idx * 3 + 2];
					byte a = paletteA[idx];
					buffer.put(a).put(b).put(g).put(r);
				}
			} else {
				for (int i = 1, n = curLine.length; i < n; i += 1) {
					int idx = curLine[i] & 255;
					byte r = palette[idx * 3 + 0];
					byte g = palette[idx * 3 + 1];
					byte b = palette[idx * 3 + 2];
					byte a = (byte) 0xFF;
					buffer.put(a).put(b).put(g).put(r);
				}
			}
		}

		private void copyPALtoRGBA(ByteBuffer buffer, byte[] curLine) {
			if (paletteA != null) {
				for (int i = 1, n = curLine.length; i < n; i += 1) {
					int idx = curLine[i] & 255;
					byte r = palette[idx * 3 + 0];
					byte g = palette[idx * 3 + 1];
					byte b = palette[idx * 3 + 2];
					byte a = paletteA[idx];
					buffer.put(r).put(g).put(b).put(a);
				}
			} else {
				for (int i = 1, n = curLine.length; i < n; i += 1) {
					int idx = curLine[i] & 255;
					byte r = palette[idx * 3 + 0];
					byte g = palette[idx * 3 + 1];
					byte b = palette[idx * 3 + 2];
					byte a = (byte) 0xFF;
					buffer.put(r).put(g).put(b).put(a);
				}
			}
		}

		private void copyPALtoBGRA(ByteBuffer buffer, byte[] curLine) {
			if (paletteA != null) {
				for (int i = 1, n = curLine.length; i < n; i += 1) {
					int idx = curLine[i] & 255;
					byte r = palette[idx * 3 + 0];
					byte g = palette[idx * 3 + 1];
					byte b = palette[idx * 3 + 2];
					byte a = paletteA[idx];
					buffer.put(b).put(g).put(r).put(a);
				}
			} else {
				for (int i = 1, n = curLine.length; i < n; i += 1) {
					int idx = curLine[i] & 255;
					byte r = palette[idx * 3 + 0];
					byte g = palette[idx * 3 + 1];
					byte b = palette[idx * 3 + 2];
					byte a = (byte) 0xFF;
					buffer.put(b).put(g).put(r).put(a);
				}
			}
		}

		private void expand4(byte[] src, byte[] dst) {
			for (int i = 1, n = dst.length; i < n; i += 2) {
				int val = src[1 + (i >> 1)] & 255;
				switch (n - i) {
				default:
					dst[i + 1] = (byte) (val & 15);
				case 1:
					dst[i] = (byte) (val >> 4);
				}
			}
		}

		private void expand2(byte[] src, byte[] dst) {
			for (int i = 1, n = dst.length; i < n; i += 4) {
				int val = src[1 + (i >> 2)] & 255;
				switch (n - i) {
				default:
					dst[i + 3] = (byte) ((val) & 3);
				case 3:
					dst[i + 2] = (byte) ((val >> 2) & 3);
				case 2:
					dst[i + 1] = (byte) ((val >> 4) & 3);
				case 1:
					dst[i] = (byte) ((val >> 6));
				}
			}
		}

		private void expand1(byte[] src, byte[] dst) {
			for (int i = 1, n = dst.length; i < n; i += 8) {
				int val = src[1 + (i >> 3)] & 255;
				switch (n - i) {
				default:
					dst[i + 7] = (byte) ((val) & 1);
				case 7:
					dst[i + 6] = (byte) ((val >> 1) & 1);
				case 6:
					dst[i + 5] = (byte) ((val >> 2) & 1);
				case 5:
					dst[i + 4] = (byte) ((val >> 3) & 1);
				case 4:
					dst[i + 3] = (byte) ((val >> 4) & 1);
				case 3:
					dst[i + 2] = (byte) ((val >> 5) & 1);
				case 2:
					dst[i + 1] = (byte) ((val >> 6) & 1);
				case 1:
					dst[i] = (byte) ((val >> 7));
				}
			}
		}

		private void unfilter(byte[] curLine, byte[] prevLine)
				throws IOException {
			switch (curLine[0]) {
			case 0: // none
				break;
			case 1:
				unfilterSub(curLine);
				break;
			case 2:
				unfilterUp(curLine, prevLine);
				break;
			case 3:
				unfilterAverage(curLine, prevLine);
				break;
			case 4:
				unfilterPaeth(curLine, prevLine);
				break;
			default:
				throw new IOException("invalide filter type in scanline: "
						+ curLine[0]);
			}
		}

		private void unfilterSub(byte[] curLine) {
			final int bpp = this.bytesPerPixel;
			for (int i = bpp + 1, n = curLine.length; i < n; ++i) {
				curLine[i] += curLine[i - bpp];
			}
		}

		private void unfilterUp(byte[] curLine, byte[] prevLine) {
			for (int i = 1, n = curLine.length; i < n; ++i) {
				curLine[i] += prevLine[i];
			}
		}

		private void unfilterAverage(byte[] curLine, byte[] prevLine) {
			final int bpp = this.bytesPerPixel;

			int i;
			for (i = 1; i <= bpp; ++i) {
				curLine[i] += (byte) ((prevLine[i] & 0xFF) >>> 1);
			}
			for (int n = curLine.length; i < n; ++i) {
				curLine[i] += (byte) (((prevLine[i] & 0xFF) + (curLine[i - bpp] & 0xFF)) >>> 1);
			}
		}

		private void unfilterPaeth(byte[] curLine, byte[] prevLine) {
			final int bpp = this.bytesPerPixel;

			int i;
			for (i = 1; i <= bpp; ++i) {
				curLine[i] += prevLine[i];
			}
			for (int n = curLine.length; i < n; ++i) {
				int a = curLine[i - bpp] & 255;
				int b = prevLine[i] & 255;
				int c = prevLine[i - bpp] & 255;
				int p = a + b - c;
				int pa = p - a;
				if (pa < 0)
					pa = -pa;
				int pb = p - b;
				if (pb < 0)
					pb = -pb;
				int pc = p - c;
				if (pc < 0)
					pc = -pc;
				if (pa <= pb && pa <= pc)
					c = a;
				else if (pb <= pc)
					c = b;
				curLine[i] += (byte) c;
			}
		}

		private void readIHDR() throws IOException {
			checkChunkLength(13);
			readChunk(buffer, 0, 13);
			width = readInt(buffer, 0);
			height = readInt(buffer, 4);
			bitdepth = buffer[8] & 255;
			colorType = buffer[9] & 255;

			switch (colorType) {
			case COLOR_GREYSCALE:
				if (bitdepth != 8) {
					throw new IOException("Unsupported bit depth: " + bitdepth);
				}
				bytesPerPixel = 1;
				break;
			case COLOR_GREYALPHA:
				if (bitdepth != 8) {
					throw new IOException("Unsupported bit depth: " + bitdepth);
				}
				bytesPerPixel = 2;
				break;
			case COLOR_TRUECOLOR:
				if (bitdepth != 8) {
					throw new IOException("Unsupported bit depth: " + bitdepth);
				}
				bytesPerPixel = 3;
				break;
			case COLOR_TRUEALPHA:
				if (bitdepth != 8) {
					throw new IOException("Unsupported bit depth: " + bitdepth);
				}
				bytesPerPixel = 4;
				break;
			case COLOR_INDEXED:
				switch (bitdepth) {
				case 8:
				case 4:
				case 2:
				case 1:
					bytesPerPixel = 1;
					break;
				default:
					throw new IOException("Unsupported bit depth: " + bitdepth);
				}
				break;
			default:
				throw new IOException("unsupported color format: " + colorType);
			}

			if (buffer[10] != 0) {
				throw new IOException("unsupported compression method");
			}
			if (buffer[11] != 0) {
				throw new IOException("unsupported filtering method");
			}
			if (buffer[12] != 0) {
				throw new IOException("unsupported interlace method");
			}
		}

		private void readPLTE() throws IOException {
			int paletteEntries = chunkLength / 3;
			if (paletteEntries < 1 || paletteEntries > 256
					|| (chunkLength % 3) != 0) {
				throw new IOException("PLTE chunk has wrong length");
			}
			palette = new byte[paletteEntries * 3];
			readChunk(palette, 0, palette.length);
		}

		private void readtRNS() throws IOException {
			switch (colorType) {
			case COLOR_GREYSCALE:
				checkChunkLength(2);
				transPixel = new byte[2];
				readChunk(transPixel, 0, 2);
				break;
			case COLOR_TRUECOLOR:
				checkChunkLength(6);
				transPixel = new byte[6];
				readChunk(transPixel, 0, 6);
				break;
			case COLOR_INDEXED:
				if (palette == null) {
					throw new IOException("tRNS chunk without PLTE chunk");
				}
				paletteA = new byte[palette.length / 3];
				Arrays.fill(paletteA, (byte) 0xFF);
				readChunk(paletteA, 0, paletteA.length);
				break;
			default:
			}
		}

		private void closeChunk() throws IOException {
			if (chunkRemaining > 0) {
				skip(chunkRemaining + 4);
			} else {
				readFully(buffer, 0, 4);
				int expectedCrc = readInt(buffer, 0);
				int computedCrc = (int) crc.getValue();
				if (computedCrc != expectedCrc) {
					throw new IOException("Invalid CRC");
				}
			}
			chunkRemaining = 0;
			chunkLength = 0;
			chunkType = 0;
		}

		private void openChunk() throws IOException {
			readFully(buffer, 0, 8);
			chunkLength = readInt(buffer, 0);
			chunkType = readInt(buffer, 4);
			chunkRemaining = chunkLength;
			crc.reset();
			crc.update(buffer, 4, 4);
		}

		private void openChunk(int expected) throws IOException {
			openChunk();
			if (chunkType != expected) {
				throw new IOException("Expected chunk: "
						+ Integer.toHexString(expected));
			}
		}

		private void checkChunkLength(int expected) throws IOException {
			if (chunkLength != expected) {
				throw new IOException("Chunk has wrong size");
			}
		}

		private int readChunk(byte[] buffer, int offset, int length)
				throws IOException {
			if (length > chunkRemaining) {
				length = chunkRemaining;
			}
			readFully(buffer, offset, length);
			crc.update(buffer, offset, length);
			chunkRemaining -= length;
			return length;
		}

		private void refillInflater(Inflater inflater) throws IOException {
			while (chunkRemaining == 0) {
				closeChunk();
				openChunk(IDAT);
			}
			int read = readChunk(buffer, 0, buffer.length);
			inflater.setInput(buffer, 0, read);
		}

		private void readChunkUnzip(Inflater inflater, byte[] buffer,
				int offset, int length) throws IOException {
			try {
				do {
					int read = inflater.inflate(buffer, offset, length);
					if (read <= 0) {
						if (inflater.finished()) {
							throw new EOFException();
						}
						if (inflater.needsInput()) {
							refillInflater(inflater);
						} else {
							throw new IOException("Can't inflate " + length
									+ " bytes");
						}
					} else {
						offset += read;
						length -= read;
					}
				} while (length > 0);
			} catch (DataFormatException ex) {
				throw (IOException) (new IOException("inflate error")
						.initCause(ex));
			}
		}

		private void readFully(byte[] buffer, int offset, int length)
				throws IOException {
			do {
				int read = input.read(buffer, offset, length);
				if (read < 0) {
					throw new EOFException();
				}
				offset += read;
				length -= read;
			} while (length > 0);
		}

		private int readInt(byte[] buffer, int offset) {
			return ((buffer[offset]) << 24)
					| ((buffer[offset + 1] & 255) << 16)
					| ((buffer[offset + 2] & 255) << 8)
					| ((buffer[offset + 3] & 255));
		}

		private void skip(long amount) throws IOException {
			while (amount > 0) {
				long skipped = input.skip(amount);
				if (skipped < 0) {
					throw new EOFException();
				}
				amount -= skipped;
			}
		}

		private static boolean checkSignature(byte[] buffer) {
			for (int i = 0; i < SIGNATURE.length; i++) {
				if (buffer[i] != SIGNATURE[i]) {
					return false;
				}
			}
			return true;
		}
	}

	private int width;

	private int height;

	private int texHeight;

	private int texWidth;

	private int bitDepth;

	private boolean hasAlpha;

	private ByteBuffer scratch;

	public int getDepth() {
		return bitDepth;
	}

	public ByteBuffer getImageBufferData() {
		return scratch;
	}

	public int getTexHeight() {
		return texHeight;
	}

	public int getTexWidth() {
		return texWidth;
	}

	public ByteBuffer loadImage(InputStream fis) throws IOException {
		return loadImage(fis, false, null);
	}

	public ByteBuffer loadImage(InputStream fis, boolean flipped,
			int[] transparent) throws IOException {
		return loadImage(fis, flipped, false, transparent);
	}

	public ByteBuffer loadImage(InputStream fis, boolean flipped,
			boolean forceAlpha, int[] transparent) throws IOException {

		PNGDecoder decoder = new PNGDecoder(fis);

		if (!decoder.isRGB()) {
			throw new IOException(
					"Only RGB formatted images are supported by the PNGLoader");
		}

		width = decoder.getWidth();
		height = decoder.getHeight();
		texWidth = GLEx.toPowerOfTwo(width);
		texHeight = GLEx.toPowerOfTwo(height);
		hasAlpha = decoder.hasAlpha();

		int perPixel = decoder.hasAlpha() ? 4 : 3;
		bitDepth = decoder.hasAlpha() ? 32 : 24;

		scratch = BufferUtils.createByteBuffer(texWidth * texHeight * perPixel);
		decoder.decode(scratch, texWidth * perPixel,
				perPixel == 4 ? PNGDecoder.RGBA : PNGDecoder.RGB);

		if (height < texHeight - 1) {
			int topOffset = (texHeight - 1) * (texWidth * perPixel);
			int bottomOffset = (height - 1) * (texWidth * perPixel);
			for (int x = 0; x < texWidth; x++) {
				for (int i = 0; i < perPixel; i++) {
					scratch.put(topOffset + x + i, scratch.get(x + i));
					scratch.put(bottomOffset + (texWidth * perPixel) + x + i,
							scratch.get(bottomOffset + x + i));
				}
			}
		}
		if (width < texWidth - 1) {
			for (int y = 0; y < texHeight; y++) {
				for (int i = 0; i < perPixel; i++) {
					scratch.put(((y + 1) * (texWidth * perPixel)) - perPixel
							+ i, scratch.get(y * (texWidth * perPixel) + i));
					scratch.put(
							(y * (texWidth * perPixel)) + (width * perPixel)
									+ i,
							scratch.get((y * (texWidth * perPixel))
									+ ((width - 1) * perPixel) + i));
				}
			}
		}

		if (!decoder.hasAlpha() && forceAlpha) {
			ByteBuffer temp = BufferUtils.createByteBuffer(texWidth * texHeight
					* 4);
			for (int x = 0; x < texWidth; x++) {
				for (int y = 0; y < texHeight; y++) {
					int srcOffset = (y * 3) + (x * texHeight * 3);
					int dstOffset = (y * 4) + (x * texHeight * 4);

					temp.put(dstOffset, scratch.get(srcOffset));
					temp.put(dstOffset + 1, scratch.get(srcOffset + 1));
					temp.put(dstOffset + 2, scratch.get(srcOffset + 2));
					if ((x < getHeight()) && (y < getWidth())) {
						temp.put(dstOffset + 3, (byte) 255);
					} else {
						temp.put(dstOffset + 3, (byte) 0);
					}
				}
			}

			bitDepth = 32;
			scratch = temp;
		}
		if (transparent != null) {
			for (int i = 0; i < texWidth * texHeight * 4; i += 4) {
				boolean match = true;
				for (int c = 0; c < 3; c++) {
					if (toInt(scratch.get(i + c)) != transparent[c]) {
						match = false;
					}
				}

				if (match) {
					scratch.put(i + 3, (byte) 0);
				}
			}
		}
		scratch.position(0);

		return scratch;
	}

	private int toInt(byte b) {
		if (b < 0) {
			return 256 + b;
		}

		return b;
	}

	public void configureEdging(boolean edging) {
	}

	public int getWidth() {
		return width;
	}

	public int getHeight() {
		return height;
	}

	public boolean hasAlpha() {
		return hasAlpha;
	}

}
